這邊針對商品部分 Service 寫一些單元測試,下面先列出預計測試的名稱,主要根據實際方法內會出現判斷的條件去設計,嘗試讓每個 Service 單元測試都 100%。
先注入並且初始化我們需要用到的Bean,這邊主要測試 ProductService ,所以加上 @InjectMocks 註解,其他應用到的 Dao 都用 @Mock 標住用 mock 產生虛擬元件注入有標住 @InjectMocks 的 ProductService,@BeforeEach 先初始化待會會測試用到的一些物件資料。
@ExtendWith(MockitoExtension.class)
public class ProductServiceTest {
    @Mock
    private ProductDao productDao;
    @InjectMocks
    private ProductService productService;
    private Product mockProduct1;
    private Product mockProduct2;
    private ProductRequest mockProductRequest;
    private List<Product> mockProducts;
    @BeforeEach
    void setUp() {
        mockProduct1 = new Product();
        mockProduct1.setId(1);
        mockProduct1.setProductName("Test Product");
        mockProduct1.setUnitPrice(10.0);
        mockProduct1.setUnitsInStock(100);
        mockProduct1.setDiscontinued(false);
        Supplier supplier1 = new Supplier();
        supplier1.setId(1);
        mockProduct1.setSupplier(supplier1);
        mockProduct2 = new Product();
        mockProduct2.setId(2);
        mockProduct2.setProductName("Test Wireless Mouse");
        mockProduct2.setDiscontinued(false);
        Supplier supplier2 = new Supplier();
        supplier2.setId(2);
        mockProduct2.setSupplier(supplier2);
        mockProduct2.setUnitPrice(24.99);
        mockProduct2.setUnitsInStock(103);
        mockProductRequest = new ProductRequest();
        mockProductRequest.setProductName("New/Update Product");
        mockProductRequest.setUnitPrice(15.0);
        mockProductRequest.setUnitsInStock(50);
        mockProductRequest.setDiscontinued(false);
        mockProductRequest.setSupplier(supplier1);
    }
		// 取得所有商品      
    @Test
    public void testGetAllProducts() {}
		// 取得特定 id 商品      
    @Test
    public void testGetProductById() {}
    // 取得特定 id 商品,找不到該 id 之商品
    @Test
    void testGetProductById_NotFound() {}
    // 創建商品
    @Test
    void testCreateProduct() {}
    // 更新商品
    @Test
    void testUpdateProduct() {}
    // 更新商品,找不到該 id 之產品
    @Test
    void testUpdateProduct_NotFound() {}
    // 刪除商品
    @Test
    void testDeleteProduct() {}
    // 搜尋產品並排序
    @Test
    void testSearchAndSortProducts() {}
    // 搜尋產品並排序,傳入商品名參數為空
    @Test
    void testSearchAndSortProducts_NullOrEmptyProductName() {}
}
關於基本查詢相關
testGetAllProducts
testGetProductById
testGetProductById_NotFound
  	@Test
    void testSearchAndSortProducts() {
        mockProducts = Arrays.asList(mockProduct2, mockProduct1);
        Page<Product> productPage = new PageImpl<>(mockProducts);
        when(productDao.findByProductNameContainingIgnoreCase(eq("Test"), any(PageRequest.class)))
                .thenReturn(productPage);
        List<Product> result = productService.searchAndSortProducts("Test", "id", "desc", 0, 10);
        assertEquals(2, result.size());
        assertEquals(mockProduct2, result.get(0));
        assertEquals(mockProduct1, result.get(1));
        verify(productDao).findByProductNameContainingIgnoreCase(eq("Test"), any(PageRequest.class));
    }
    @Test
    void testSearchAndSortProducts_NullOrEmptyProductName() {
        mockProducts = Arrays.asList(mockProduct1, mockProduct2);
        Page<Product> productPage = new PageImpl<>(mockProducts);
        when(productDao.findAll(any(PageRequest.class))).thenReturn(productPage);
        // null product name
        List<Product> resultNull = productService.searchAndSortProducts(null, "id", "asc", 0, 10);
        assertEquals(2, resultNull.size());
        assertEquals(mockProduct1, resultNull.get(0));
        assertEquals(mockProduct2, resultNull.get(1));
        // empty product name
        List<Product> resultEmpty = productService.searchAndSortProducts("", "id", "asc", 0, 10);
        assertEquals(2, resultEmpty.size());
        assertEquals(mockProduct1, resultEmpty.get(0));
        assertEquals(mockProduct2, resultNull.get(1));
        
        verify(productDao, times(2)).findAll(any(PageRequest.class));
    }
關於新增、刪除、修改相關
testCreateProduct
testUpdateProduct
testUpdateProduct_NotFound
testDeleteProduct
@Test
    void testCreateProduct() {
        Product newMockProduct = productService.convertToModel(mockProductRequest);
        when(productDao.save(any(Product.class))).thenReturn(newMockProduct);
        Product createdProduct = productService.createProduct(mockProductRequest);
        assertNotNull(createdProduct);
        assertEquals("New/Update Product", createdProduct.getProductName());
        verify(productDao, times(1)).save(any(Product.class));
    }
    @Test
    void testUpdateProduct() {
        Product updateMcokProduct = productService.convertToModel(mockProductRequest);
        when(productDao.findById(1)).thenReturn(Optional.of(mockProduct1));
        when(productDao.save(any(Product.class))).thenReturn(updateMcokProduct);
        Product updatedProduct = productService.updateProduct(1, mockProductRequest);
        assertNotNull(updatedProduct);
        assertEquals("New/Update Product", updatedProduct.getProductName());
        verify(productDao, times(1)).findById(1);
        verify(productDao, times(1)).save(any(Product.class));
    }
    @Test
    void testUpdateProduct_NotFound() {
        when(productDao.findById(3)).thenReturn(Optional.empty());
        
        Product updatedProduct = productService.updateProduct(3, mockProductRequest);
        
        assertNull(updatedProduct);
        verify(productDao, times(1)).findById(3);
        verify(productDao, never()).save(any(Product.class));
    }
    @Test
    void testDeleteProduct() {
        doNothing().when(productDao).deleteById(1);
        
        productService.deleteProductById(1);
        
        verify(productDao, times(1)).deleteById(1);
    }
搜尋相關
testSearchAndSortProducts
testSearchAndSortProducts_NullOrEmptyProductName
@Test
    public void testGetAllProducts() {
        mockProducts = Arrays.asList(mockProduct1, mockProduct2);
        when(productDao.findAll()).thenReturn(mockProducts);
        
        List<Product> products = productService.getAllProducts();
        
        assertEquals(products.get(0).getProductName(), "Test Product");
        assertEquals(products.get(1).getProductName(), "Test Wireless Mouse");
        assertTrue(products.size() == 2);
        verify(productDao, times(1)).findAll();
    }
    @Test
    public void testGetProductById() {
        when(productDao.findById(1)).thenReturn(Optional.of(mockProduct1));
        Optional<Product> product = productService.getProductById(1);
        assertTrue(product.isPresent());
        assertEquals(product.get().getProductName(), "Test Product");
        assertEquals(product.get().getUnitPrice(), 10.0);
        assertEquals(product.get().getUnitsInStock(), 100);
        verify(productDao, times(1)).findById(1);
    }
    @Test
    void testGetProductById_NotFound() {
        when(productDao.findById(3)).thenReturn(Optional.empty());
        Optional<Product> product = productService.getProductById(3);
        assertFalse(product.isPresent());
        verify(productDao, times(1)).findById(3);
    }
再來針對新建訂單部分 Service 的單元測試
盡量讓內部邏輯可以都被測驗到,每個方法裡的判斷都有走過一遍,這樣測試覆蓋率高也能確保程式運作正常。
先注入並且初始化我們需要用到的Bean,這邊主要測試 OrderService,所以加上 @InjectMocks 註解,其他應用到的 Dao 都用 @Mock 標住用 mock 產生虛擬元件注入有標住 @InjectMocks 的 OrderService
預計可以拆成下面這些項目:
@ExtendWith(MockitoExtension.class)
public class OrderServiceTest {
    @InjectMocks
    private OrderService orderService;
    @Mock
    private OrderInfoDao orderInfoDao;
    @Mock
    private OrderItemDao orderItemDao;
    @Mock
    private ProductDao productDao;
    @Test
    void testCreateOrder_Success() {
	    // 正確創建訂單      
    }
    @Test
    void testCreateOrder_NotFoundProduct() {
	    // 訂單內商品不存在
    }
    @Test
    void testCreateOrder_InsufficientStock() {
	    // 訂單內商品目前庫存不足
    }
}
createOrder_Success: 測試正確創建訂單
@Test
void testCreateOrder_Success() {
    // Arrange
    Integer userId = 1;
    CreateOrderInfoRequest request = new CreateOrderInfoRequest();
    BuyItem buyItem = new BuyItem();
    buyItem.setProductId(1);
    buyItem.setQuantity(2);
    request.setBuyItemList(Arrays.asList(buyItem));
    Product product = new Product();
    product.setId(1);
    product.setProductName("Test Product");
    product.setUnitPrice(10.0);
    product.setUnitsInStock(5);
    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setId(1);
    orderInfo.setUserId(userId);
    orderInfo.setTotalAmount(20.0);
    OrderItem orderItem = new OrderItem();
    orderItem.setId(1);
    orderItem.setOrderInfoId(1);
    orderItem.setProductId(1);
    orderItem.setQuantity(2);
    orderItem.setAmount(10.0);
    when(productDao.findById(1)).thenReturn(Optional.of(product));
    when(orderInfoDao.save(any())).thenReturn(orderInfo);
    when(orderItemDao.saveAll(any())).thenReturn(Arrays.asList(orderItem));
    CreateOrderResponse response = orderService.createOrder(userId, request);
    assertNotNull(response);
    verify(productDao, times(1)).save(any());
    verify(productDao).save(argThat(savedProduct ->
            savedProduct.getId().equals(1) && savedProduct.getUnitsInStock() == 3
    ));
    verify(orderInfoDao, times(1)).save(any());
    verify(orderItemDao, times(1)).saveAll(any());
}
createOrder_ProductNotFound: 測試訂單內商品不存在
ProductDao 返回空的 Optional
@Test
    void testCreateOrder_ProductNotFound() {
        Integer userId = 1;
        CreateOrderInfoRequest request = new CreateOrderInfoRequest();
        BuyItem buyItem = new BuyItem();
        buyItem.setProductId(1);
        buyItem.setQuantity(2);
        request.setBuyItemList(Arrays.asList(buyItem));
        when(productDao.findById(1)).thenReturn(Optional.empty());
        assertThrows(ResponseStatusException.class, () -> orderService.createOrder(userId, request));
    }
createOrder_InsufficientStock: 測試訂單內商品目前庫存不足
@Test
    void testCreateOrder_InsufficientStock() {
        Integer userId = 1;
        CreateOrderInfoRequest request = new CreateOrderInfoRequest();
        BuyItem buyItem = new BuyItem();
        buyItem.setProductId(1);
        buyItem.setQuantity(10);
        request.setBuyItemList(Arrays.asList(buyItem));
        Product product = new Product();
        product.setId(1);
        product.setProductName("Test Product");
        product.setUnitPrice(10.0);
        product.setUnitsInStock(1);
        when(productDao.findById(1)).thenReturn(Optional.of(product));
        ResponseStatusException exception = assertThrows(ResponseStatusException.class,
                () -> orderService.createOrder(userId, request));
        assertEquals(HttpStatus.BAD_REQUEST, exception.getStatusCode());
    }
目前訂單部分測試分享到這邊,針對商品部分因為和先前介紹單元測試那邊類似就沒有多去寫,但開發上可以盡量把重要的功能都涵蓋是最好的。
相關文章也會同步更新我的部落格,有興趣也可以在裡面找其他的技術分享跟資訊。